#!/usr/bin/python

#
# Multiview -- a simple curses interface to a multiple stream of text
# interaction.  Makes for a nice debugging environment.
#
# The output of the subprocess is interpreted as follows:
#
# tag ' ' arbitrary text '\n'
#
# special tag '#' are metacontrol messages for multiview
#
import curses
import curses.textpad
import sys
import subprocess

class Layout:
    HORZ = 1                            # split horizontally
    VERT = 2                            # split vertically
    
    def __init__(self):
        self.type = Layout.HORZ

    def do_layout(self, main):
        views
        selectbar
        commandbox

class View:
    def __init__(self, name,
                 size_rows, size_cols, origin_row, origin_col):
        self.name = name
        self.visible = False
        
        self.rows = size_rows
        self.cols = size_cols
        self.origin_row = origin_row
        self.origin_col = origin_col

        self.scroll_win = curses.newwin(self.rows, 1, origin_row,
                                        origin_col + self.cols - 1)
        self.main_win = curses.newwin(self.rows, self.cols - 1,
                                      self.origin_row, self.origin_col)
        self.text = [10 * str(i) for i in xrange(40)]
        self.cur_top_line = 0

    def reposition(self, size_rows, size_cols, origin_row, origin_col):
        raise NotImplemented()
        
    def scroll_to(self, line):
        if line < 0:
            line = 0
        elif (len(self.text) - line) < self.rows:
            # XXX/bowei - maybe off by a little bit
            line = max(0, len(self.text) - self.rows)
        self.main_win.erase()
        for r in xrange(min(self.rows, len(self.text))):
            self.main_win.addstr(r, 0, self.text[r + line])
        self.cur_top_line = line
        self.draw_scrollbar(float(self.cur_top_line)/(len(self.text) - self.rows))

    def scroll_to_end(self):
        self.scroll_to(len(self.text) - self.rows)
        
    def draw_scrollbar(self, frac):
        if frac > 1:
            frac = 1
        elif frac < 0:
            frac = 0
        rows, cols = self.scroll_win.getmaxyx()
        for y in xrange(rows):
            # for some retarded reason, curses raises an error if you
            # write to the last column
            try:
                self.scroll_win.addch(y, 0, ' ', curses.A_REVERSE)
            except curses.error: pass
        try:
            self.scroll_win.addch(int((rows - 1) * frac), 0, '#')
        except curses.error: pass

    def refresh(self):
        self.scroll_win.refresh()
        self.main_win.refresh()

class SelectBar:
    def __init__(self, row, cols):
        self.row = row
        self.cols = cols
        self.win = curses.newwin(self.row, self.cols, self.row, 0)
        self.choices = []
        self.selected = []

    def select(self, item):
        assert item in self.choices
        if item not in self.selected:
            self.selected.append(item)

    def unselect(self, item):
        assert item in self.choices
        if item in self.selected:
            self.selected.remove(item)

    def add_item(self, item):
        assert item not in self.choices
        self.choices.append(item)

    def rm_item(self, item):
        assert item in self.choices
        self.choices.remove(item)

    def refresh(self):
        self.win.erase()
        self.win.move(0,0)

        if len(self.choices) == 0:
            self.win.addstr('<empty>', curses.A_REVERSE)
        else:
            format = '| %s '
            for i in self.choices:
                if i in self.selected:
                    self.win.addstr(format % i)
                else:
                    self.win.addstr(format % i, curses.A_REVERSE)
                format = '| %s '
        self.win.refresh()

class CommandBox:
    """Window in which commands are editted"""
    def __init__(self, size_rows, size_cols, origin_rows, origin_cols):
        self.size_rows = size_rows
        self.size_cols = size_cols
        self.origin_rows = origin_rows
        self.origin_cols = origin_cols

        self.win = curses.newwin(self.size_rows, self.size_cols,
                                 self.origin_rows, self.origin_cols)
        self.textbox = curses.textpad.Textbox(self.win)

    def edit(self):
        curses.curs_set(1)
        self.win.move(0, 0)
        self.win.clrtoeol()
        t = self.textbox.edit()
        curses.curs_set(0)
        return t
        
class Keymap:
    """Maintains the keymaps"""
    def __init__(self, main):
        self.main = main
        self.keymap = {}
        self.default_keymap()
        
    def default_keymap(self):
        pass
        
class Main:
    def __init__(self, scr):
        self.scr = scr
        rows, cols = self.scr.getmaxyx()
        self.messages_view = 
        self.views = []
        self.select = SelectBar(rows - 2, cols)
        self.command = CommandBox(1, cols, rows - 1, 0)
        self.keymap = Keymap(self)

    def parse_args(self):
        self.cmd = sys.argv[1:]
        # XXX/bowei -- usage, blah blah
        
    def run(self):
        curses.curs_set(0)

        # test test 
        self.line = 0

        self.select.add_item('alice')
        self.select.add_item('bob')
        self.select.add_item('carl')

        self.select.select('bob')
        self.refresh()
        # test test

        self.proc = subprocess.Popen(self.cmd,
                                     stdin=subprocess.PIPE,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE,
                                     shell=False,
                                     close_fds=True)
        while True:
            c = self.scr.getch()
            if c == ord('!'):
                s = self.command.edit()
                self.text.text[0] = s
            elif c == ord('q'):
                break
            self.text.scroll_to(self.line)
            self.refresh()

    def refresh(self):
        self.scr.refresh()        
        self.text.refresh()
        self.select.refresh()

    def add_view(self, view):
        self.views.append(view)
        self.select.add_item(view.name)

def run(stdscr):
    main = Main(stdscr)
    main.run()

curses.wrapper(run)
